#ifndef SG4C_RUNTIME_H_
#define SG4C_RUNTIME_H_
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include <math.h>

#include "sg4c-proto.h"


void writeInt(int val){
    fprintf(stdout, "%x\t", val);
}

void writeFloat(float val){
    fprintf(stdout, "%a\t", val);
}

void writeDouble(double val){
    fprintf(stdout, "%la\t", val);
}

void writeString(const char * str){
    assert( '\0'==strchr(str, '\t') );
    assert( '\0'==strchr(str, '\n') );
    writeInt(strlen(str));
    fputs(str, stdout);
    fputc('\t', stdout);
}

int readInt(){
    uint32_t val;
    if( 1!=fscanf(stdin, "%x", &val) ){
        fputs("ERR: read int\n", stderr);
        exit(1);
    }
    return val;
}


float readFloat(){
    float val;
    if( 1!=fscanf(stdin, "%a", &val) ){
        exit(1);
    }
    return val;
}


double readDouble(){
    double val;
    if( 1!=fscanf(stdin, "%la", &val) ){
        exit(1);
    }
    return val;

}


char * readString(){
    int len = readInt();
    if( fgetc(stdin)!='\n' ){
        exit(1);
    }
    char * buf = (char *)malloc(len+1);
    int readed=0;

    if( !fgets(buf+readed, len+1 ,stdin) ){
        exit(1);
    }
    readed=strlen(buf);
    while( readed < len && !feof(stdin) ){
        if( !fgets(buf+readed, len-readed+1 ,stdin) ){
            exit(1);
        }
        //printf("%d\n", readed);
        readed=strlen(buf);
        //puts(buf);
    }
    buf[len]='\0';
    return buf;
}



////////////////////////////////////////////////////////////////////////
// Misc
//

void sgFlush(){
    fputc('\n', stdout);
    fflush(stdout);
}

////////////////////////////////////////////////////////////////////////
// Setup
//

static sg_config_t __sg_current_config;

void sgInitConfig(sg_config_t * p){
    memset(p, 0, sizeof(sg_config_t) );
}

int sgWidth(){
    return __sg_current_config.screen.width;
}

int sgHeight(){
    return __sg_current_config.screen.height;
}

void sgSetupScreen(){
    //writeString(".setup.screen");
    writeInt(SG_SETUP_SCREEN);
    writeInt(__sg_current_config.screen.width);
    writeInt(__sg_current_config.screen.height);
}

void sgSetupFramerate(){
    //writeString(".setup.fps");
    writeInt(SG_SETUP_FPS);
    writeInt(__sg_current_config.framerate);
}

void sgEndSetup(){
    writeInt(SG_END);
    //writeString(".end");
    sgFlush();
}

void sgSetupSmooth(){
    if( __sg_current_config.smooth ){
        writeInt(SG_SETUP_SMOOTH);
        //writeString(".setup.smooth");
    }
}

void sgSetup(const sg_config_t * config){
    __sg_current_config = *config;
    sgSetupScreen();
    sgSetupFramerate();
    sgSetupSmooth();
    sgEndSetup();
}

void sgEndSetupGraphics(){
    //writeString(".end");
    writeInt(SG_END);
    sgFlush();
}


////////////////////////////////////////////////////////////////////////
// Key Event
//
static int __sg_key_id = 0;
static sg_key_t __sg_key[2];

sg_key_t sgKey(){
    return __sg_key[__sg_key_id];
}

sg_key_t sgKeyPrevious(){
    return __sg_key[(__sg_key_id+1)%2];
}

sg_key_t * __sgKeyCurrent(){
    return &__sg_key[__sg_key_id];
}

void __sgUpdateKey(){
    __sg_key_id = (__sg_key_id+1)%2;
}

////////////////////////////////////////////////////////////////////////
// Mouse Event
//
static int __sg_mouse_id = 0;
static sg_mouse_t __sg_mouse[2];

sg_mouse_t sgMouse(){
    return __sg_mouse[__sg_mouse_id];
}

sg_mouse_t sgMousePrevious(){
    return __sg_mouse[ (__sg_mouse_id+1)%2 ];
}

sg_mouse_t * __sgMouseCurrent(){
    return &__sg_mouse[__sg_mouse_id];
}

void __sgUpdateMouse(){
    __sg_mouse_id = (__sg_mouse_id+1)%2;
}
////////////////////////////////////////////////////////////////////////
// Graphics
//


void sgRedraw(){
    __sg_current_config._redraw = 0;
}

color_t sgRGBA(byte_t r, byte_t g, byte_t b, byte_t a ){
    color_t color = { r, g, b, a };
    return color;
}

color_t sgRGB(byte_t r, byte_t g, byte_t b){
    color_t color = { r, g, b, 0xFF };
    return color;
}

color_t sgGray(byte_t gray ){
    color_t color = { gray, gray, gray, 0xFF };
    return color;
}

color_t sgHexRGB( unsigned int rgb ){
    byte_t r = ( rgb >> 16 ) & 0xff;
    byte_t g = ( rgb >> 8 ) & 0xff;
    byte_t b = ( rgb >> 0 ) & 0xff;
    color_t col = { r, g, b, 0xff };
    return col;
}

color_t sgHexRGBA( unsigned int rgb ){
    byte_t r = ( rgb >> 24 ) & 0xff;
    byte_t g = ( rgb >> 16 ) & 0xff;
    byte_t b = ( rgb >> 8 ) & 0xff;
    byte_t a = ( rgb >> 0 ) & 0xff;
    color_t col = { r, g, b, a };
    return col;
}


void sgRect(int x, int y, int w, int h){
    //~ writeString(".basic.rect");
    writeInt(SG_BASIC_RECT);
    writeInt(x);
    writeInt(y);
    writeInt(w);
    writeInt(h);
}

void sgEllipse(int x, int y, int w, int h){
    //~ writeString(".basic.ellipse");
    writeInt(SG_BASIC_ELLIPSE);
    writeInt(x);
    writeInt(y);
    writeInt(w);
    writeInt(h);
}

void sgLine(int x1, int y1, int x2, int y2){
    //~ writeString(".basic.line");
    writeInt(SG_BASIC_LINE);
    writeInt(x1);
    writeInt(y1);
    writeInt(x2);
    writeInt(y2);
}

void sgPoint(int x, int y){
    //~ writeString(".basic.point");
    writeInt(SG_BASIC_POINT);
    writeInt(x);
    writeInt(y);
}

void sgStroke(color_t color){
    //writeString(".basic.strokecolor");
    writeInt(SG_BASIC_STROKECOLOR);
    writeInt(color.red);
    writeInt(color.green);
    writeInt(color.blue);
    writeInt(color.alpha);
}

void sgStrokeWeight(float weight){
    //~ writeString(".basic.stroke");
    writeInt(SG_BASIC_STROKE);
    writeFloat(weight);
}

void sgNoStroke(){
    sgStrokeWeight(0);
}

void sgNoFill(){
    //~ writeString(".basic.nofill");
    writeInt(SG_BASIC_NOFILL);
}

void sgFill(color_t color){
    //~ writeString(".basic.fillcolor");
    writeInt(SG_BASIC_FILLCOLOR);
    writeInt(color.red);
    writeInt(color.green);
    writeInt(color.blue);
    writeInt(color.alpha);
}



void sgPolygon(int npoints, ... ){
    //~ writeString(".basic.polygon");
    writeInt(SG_BASIC_POLYGON);
    writeInt(npoints);
    va_list args;
    va_start(args, npoints);
    for( ; npoints>0; --npoints){
        writeInt( va_arg(args, int) );
        writeInt( va_arg(args, int) );
    }
    va_end(args);
}

void sgPolygonArray( int xpos[], int ypos[], int npoints ){
    if( npoints<0 ) return;
    //~ writeString(".basic.polygon");
    writeInt(SG_BASIC_POLYGON);
    writeInt(npoints);
    int i;
    for ( i=0; i<npoints; ++i ){
        writeInt(xpos[i]);
        writeInt(ypos[i]);
    }
}

void sgTranslate( int x, int y ){
    //~ writeString( ".basic.translate" );
    writeInt(SG_BASIC_TRANSLATE);
    writeInt( x );
    writeInt( y );
}

void sgRotate( float angle ){
    //~ writeString( ".basic.rotate" );
    writeInt(SG_BASIC_ROTATE);
    writeFloat( angle );
}

void sgEndGraphics(){
    writeInt(SG_BASIC_END);
    //~ writeString(".end");
    sgFlush();
}



////////////////////////////////////////////////////////////////////////
// Font
//
static int __sg_font_next_id = 0;

sg_font_t sgCreateFont( char * fontname, int pt ){
    const int id = __sg_font_next_id++;
    //writeString(".font.create");
    writeInt(SG_FONT_CREATE);
    writeString(fontname);
    writeInt(id);
    writeInt(pt);

    sgFlush();


    int height = readInt();


    sg_font_t font = { id, height };
    return font;
}

void sgFont(sg_font_t font){
    //~ writeString( ".basic.font" );
    writeInt(SG_BASIC_FONT);
    writeInt(font.__id);
}

void sgText(const char * text, int x, int y){
    //~ writeString(".basic.text");
    writeInt(SG_BASIC_TEXT);
    writeString(text);
    writeInt(x);
    writeInt(y);
}



////////////////////////////////////////////////////////////////////////

void __sgHandleMousePos(){
    int mx = readInt();
    int my = readInt();

    __sgMouseCurrent()->x = mx;
    __sgMouseCurrent()->y = my;
}

void __sgHandleMouseButton(){
    int mbut = readInt();
    __sgMouseCurrent()->button = mbut;
}


void __sgHandleMouseEvent(){
    const int event = readInt();
    __sgUpdateMouse();
    switch(event){
    case SG_EVENT_MOUSE_MOVED:
        __sgHandleMousePos();
        __sg_current_config.mouseMoved();
        break;
    case SG_EVENT_MOUSE_PRESSED:
        __sgHandleMousePos();
        __sgHandleMouseButton();
        break;
    case SG_EVENT_MOUSE_ENTERED:
        __sgHandleMousePos();
        __sg_current_config.mouseEntered();
        break;
    case SG_EVENT_MOUSE_EXITED:
        __sgHandleMousePos();
        __sg_current_config.mouseExited();
        break;
    case SG_EVENT_MOUSE_RELEASED:
        __sgHandleMousePos();
        __sgHandleMouseButton();
        __sg_current_config.mouseReleased();
        break;
    case SG_EVENT_MOUSE_CLICKED:
        __sgHandleMousePos();
        __sgHandleMouseButton();
        __sg_current_config.mouseClicked();
        break;
    case SG_EVENT_MOUSE_DRAGGED:
        __sgHandleMousePos();
        __sgHandleMouseButton();
        __sg_current_config.mouseDragged();
        break;
    default:
        exit(1);
    }
}

void __sgHandleKey(){
    int keychar = readInt();
    int keycode = readInt();
    __sgKeyCurrent()->character = keychar;
    __sgKeyCurrent()->code = keycode;
}

void __sgHandleKeyEvent(){
    const int event = readInt();
    __sgUpdateKey();
    switch( event ){
    case SG_EVENT_KEY_TYPED:
        __sgHandleKey();
        __sg_current_config.keyTyped();
        break;
    case SG_EVENT_KEY_PRESSED:
        __sgHandleKey();
        __sg_current_config.keyPressed();
        break;
    case SG_EVENT_KEY_RELEASED:
        __sgHandleKey();
        __sg_current_config.keyReleased();
        break;
    default:
        exit(1);
    }
}


int sgRunForever(){
    assert( sizeof(char)==1 );

    while(1){
        const int request = readInt();
        switch(request){
        case SG_UPDATE_FRAME:
            if( __sg_current_config.framerate>0 || __sg_current_config._redraw==0 ){
                __sg_current_config._redraw = 1;
                __sg_current_config.draw();
            }
            sgEndGraphics();
            break;
        case SG_SETUP_GRAPHICS:
            __sg_current_config.setup();
            sgEndSetupGraphics();
            break;
        case SG_EVENT_MOUSE:
            __sgHandleMouseEvent();
            break;
        case SG_EVENT_KEY:
            __sgHandleKeyEvent();
            break;
        default:
            fputs("Error request type from frontend", stderr);
            //fputs(request, stderr);
            exit(1);
        }
    }

    return 0;
}

#endif  //SG4C_RUNTIME_H_
